package main

import (
	"bytes"
	"fmt"
	"os"
	"io"
	"sync"
	"time"
)

// 创建互斥锁
var mu sync.Mutex

// singleHandler 代表单次处理函数的类型，读和写用的函数内容有些不同，但是签名都是这样的
type singleHandler func() (data string, n int, err error)

// 生成一个写入当前时间的函数
func genWriter(writer io.Writer) singleHandler {
	return func() (data string, n int, err error) {
		// 准备数据
		data = fmt.Sprintf("%s\t", time.Now().Format(time.StampNano))
		// 写入数据
		mu.Lock()
		defer mu.Unlock()
		n, err = writer.Write([]byte(data))
		return
	}
}

// 生成一个读取数据的函数
func genReader(reader io.Reader) singleHandler {
	return func() (data string, n int, err error) {
		// 类型断言，把io.Reader接口转成*bytes.Buffer类型
		// 下面要调用*bytes.Buffer类型的ReadString方法
		// 因为函数的参数要求是一个接口类型，但是后面的读操作用的是*bytes.Buffer的ReadString方法
		// 所以在调用方法前，必须要检查接口的实际类型（动态类型）
		// 实际在主函数里调用genReader函数是，传入的就是*bytes.Buffer
		// 类型断言x.(T)，这里x必须为一个接口类型，但并非必须是空接口 
		// 这里reader是个io.Reader接口。如果要对非接口类型的变量做类型断言，就要先转成空接口
		buffer, ok := reader.(*bytes.Buffer)
		if !ok {
			err = fmt.Errorf("unsupported reader")
			return
		}
		// 读取数据
		mu.Lock()
		defer mu.Unlock()
		data, err = buffer.ReadString('\t')
		n = len(data)
		return
	}
}

// 处理流程配置的类型，这里把处理流程相关的信息全部写在下面的结构体类型里
type handlerConfig struct {
	handler   singleHandler // 处理函数
	goNum     int           // 要启用的goroutine的数量
	number    int           // 单个goroutine中处理的次数
	interval  time.Duration // 单个goroutine中，多次处理中间间隔的时间
	counter   int           // 数据量计数器，字节数
	counterMu sync.Mutex    // 上面的数据量计数器专用的互斥锁
}

// 增加数据量计数器的方法
func (hc *handlerConfig) count(skip int) int {
	hc.counterMu.Lock()
	defer hc.counterMu.Unlock()
	hc.counter += skip
	return hc.counter
}

func main() {
	// 创建缓冲区由于下面的读和写
	var buffer bytes.Buffer
	// 写入数据的配置，分6个goroutine分别写入4次，一个24次
	writingConfig := handlerConfig{
		handler: genWriter(&buffer),
		goNum: 6,
		number: 4,
		interval: time.Millisecond * 100,
	}
	// 读取数据的配置，分8个goroutine分别读取3次，一个也是24次
	readingConfig := handlerConfig{
		handler: genReader(&buffer),
		goNum: 8,
		number: 3,
		interval: time.Millisecond * 100,
	}

	done := make(chan struct{})

	// 启用多个goroutine对缓冲区进行多次写入
	for i := 0; i < writingConfig.goNum; i++ {
		go func(i int) {
			defer func() {
				done <- struct{}{}
			}()
			for j :=0; j < writingConfig.number; j++ {
				// 进入迭代前等待，逻辑稍微简单一点
				// 如果写在最后，那么因为err而退出时这次迭代就不会等待了
				time.Sleep(writingConfig.interval)
				data, n, err := writingConfig.handler()
				if err != nil {
					fmt.Fprintf(os.Stderr, "writer [%d-%d] ERROR: %s\n", i, j, err)
					continue
				}
				total := writingConfig.count(n)
				fmt.Printf("writer [%d-%d] Report: %s (total %d)\n", i, j, data, total)
			}
		}(i)
	}

	// 启用多个goroutine对缓冲区进行多次读取
	for i := 0; i < readingConfig.goNum; i++ {
		go func (i int) {
			defer func() {
				done <- struct{}{}
			}()
			for j := 0; j < readingConfig.number; j++ {
				var (
					data string
					n int
					err error
				)
				// 下面的无限for循环是一个代码块，里面的data、n、err如果用短变量赋值就是局部变量
				// 所以上面在代码块外面，声明了i的for循环内部的变量
				for {
					// 如果读比写快，被读的是空的，一读就到末尾了，就会返回EOF错误，
					time.Sleep(readingConfig.interval)
					data, n, err = readingConfig.handler()
					// 这个判断逻辑是读取的EOF错误，就无限循环等待。读到内容或其他错误就跳出循环
					if err == nil || err != io.EOF {
						break
					}
				}
				if err != nil {
					fmt.Fprintf(os.Stderr, "reader [%d-%d] ERROR: %s\n", i, j, err)
				}
				total := readingConfig.count(n)
				fmt.Printf("reader [%d-%d] Report: %s (total %d)\n", i, j, data, total)
			}
		}(i)
	}

	// 等待所有goroutine结束
	doneNum := writingConfig.goNum + readingConfig.goNum
	for i := 0; i < doneNum; i++ {
		<- done
	}
}
